/**
 * 
 */
package br.embedded.socialapp.business;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import org.jgrapht.Graph;
import org.jgrapht.event.EdgeTraversalEvent;
import org.jgrapht.event.TraversalListenerAdapter;
import org.jgrapht.event.VertexTraversalEvent;
import org.jgrapht.traverse.DepthFirstIterator;

import br.embedded.socialapp.domain.CustomEdge;
import br.embedded.socialapp.domain.User;

/**
 * @author daniel
 *
 */
public class GraphCache extends TraversalListenerAdapter<User, CustomEdge>
{
	private Graph<User, CustomEdge> graph;
	private Hashtable<String, Hashtable<Integer, Set<User>>> hashtable;
	
	public GraphCache(Graph<User, CustomEdge> g)
	{
		this.graph = g;
		this.hashtable = new Hashtable<String, Hashtable<Integer, Set<User>>>();
	}
	
	/* (non-Javadoc)
	 * @see org.jgrapht.event.TraversalListenerAdapter#edgeTraversed(org.jgrapht.event.EdgeTraversalEvent)
	 */
	@Override
	public void edgeTraversed(EdgeTraversalEvent<User, CustomEdge> arg0) {
		super.edgeTraversed(arg0);
		
		User v1 = graph.getEdgeSource(arg0.getEdge());
		User v2 = graph.getEdgeTarget(arg0.getEdge());
		
		if (hashtable.containsKey(v1.getLogin()))
		{
			if (!hashtable.get(v1.getLogin()).containsKey(1))
				hashtable.get(v1.getLogin()).put(1, new HashSet<User>());
			
			hashtable.get(v1.getLogin()).get(1).add(v2);
		}
		
		if (hashtable.containsKey(v2.getLogin()))
		{
			if (!hashtable.get(v2.getLogin()).containsKey(1))
				hashtable.get(v2.getLogin()).put(1, new HashSet<User>());
			
			hashtable.get(v2.getLogin()).get(1).add(v1);
		}
	}

	/* (non-Javadoc)
	 * @see org.jgrapht.event.TraversalListenerAdapter#vertexFinished(org.jgrapht.event.VertexTraversalEvent)
	 */
	@Override
	public void vertexFinished(VertexTraversalEvent<User> arg0) {
		super.vertexFinished(arg0);
		
		User vertex = arg0.getVertex();
		
		update(vertex);
		//update(vertex, 1);
	}

	private void update(User vertex) {
		if (hashtable.containsKey(vertex.getLogin()))
		{
			Set<User> vertexNeighbours = hashtable.get(vertex.getLogin()).get(1);

			if (vertexNeighbours != null && vertexNeighbours.size() > 0)
			{
				for (User neighbour : vertexNeighbours)
				{
					ArrayList<User> diffList = null;

					for (int i = 1; i <= hashtable.get(vertex.getLogin()).size(); i++)
					{
						if (!hashtable.get(vertex.getLogin()).containsKey(i) || hashtable.get(vertex.getLogin()).get(i).isEmpty())
							continue;

						if (!hashtable.get(neighbour.getLogin()).containsKey(i+1))
							hashtable.get(neighbour.getLogin()).put(i+1, new HashSet<User>());

						ArrayList<User> levels = new ArrayList<User>();

						for (int j = 1; j <= hashtable.get(neighbour.getLogin()).size(); j++)
						{
							if (hashtable.get(neighbour.getLogin()).containsKey(j)) //&& !hashtable.get(neighbour.getLogin()).get(j).isEmpty())
								levels.addAll(hashtable.get(neighbour.getLogin()).get(j));
						}

						ArrayList<User> neighbourSet = new ArrayList<User>(hashtable.get(vertex.getLogin()).get(i));

						if (neighbourSet.contains(neighbour))
							neighbourSet.remove(neighbour);

						for (User usuario : levels) {
							neighbourSet.remove(usuario);
						}

						diffList = new ArrayList<User>(neighbourSet);

						if (neighbourSet.size() == 0 && hashtable.get(neighbour.getLogin()).get(i+1).isEmpty())
						{
							hashtable.get(neighbour.getLogin()).remove(i+1);
						}

						if (hashtable.get(neighbour.getLogin()).containsKey(i+1))
						{
							hashtable.get(neighbour.getLogin()).get(i+1).addAll(diffList);

							if (i > 1)
							{
								for (User usuario : diffList) {

									if (!hashtable.get(usuario.getLogin()).containsKey(i+1))
										hashtable.get(usuario.getLogin()).put(i+1, new HashSet<User>());

									hashtable.get(usuario.getLogin()).get(i+1).add(neighbour);
								}
							}
						}
					}
				}
			}
		}
	}
	
/*	private void update(User vertex, int level) {
		if (hashtable.containsKey(vertex.getLogin()))
		{
			Set<User> vertexNeighbours = hashtable.get(vertex.getLogin()).get(level);
			
			if (vertexNeighbours != null && vertexNeighbours.size() > 0)
			{
				int nextLevel = level + 1;
				
				for (User neighbour : vertexNeighbours)
				{
					if (!hashtable.get(neighbour.getLogin()).containsKey(nextLevel))
						hashtable.get(neighbour.getLogin()).put(nextLevel, new HashSet<User>());
					
					Set<User> s1 = new HashSet<User>();
					
					ArrayList<User> n1 = new ArrayList<User>(hashtable.get(neighbour.getLogin()).get(level));
					ArrayList<User> n2 = new ArrayList<User>(hashtable.get(neighbour.getLogin()).get(nextLevel));
					
					s1.addAll(n1);
					s1.addAll(n2);
					
					ArrayList<User> s2 = new ArrayList<User>(vertexNeighbours);
					s2.remove(neighbour);
					
					for (User user : s1) {
						s2.remove(user);
					}
				
					hashtable.get(neighbour.getLogin()).get(nextLevel).addAll(s2);
					n2 = new ArrayList<User>(hashtable.get(neighbour.getLogin()).get(nextLevel));
					
					for (User user : n1) {
						if (n2.contains(user))
							n2.remove(user);
					}
					hashtable.get(neighbour.getLogin()).put(nextLevel, new HashSet<User>(n2));
					
					//System.out.println(n2);
					//System.out.println("v: " + vertex.getLogin() + " neig: " + neighbour.getLogin() +  " n1: " + hashtable.get(neighbour.getLogin()).get(level) + " n2: " + hashtable.get(neighbour.getLogin()).get(nextLevel));
					//hashtable.get(neighbour.getLogin()).get(nextLevel).remove(neighbour);
				}
			}
		}
	}
*/
	/* (non-Javadoc)
	 * @see org.jgrapht.event.TraversalListenerAdapter#vertexTraversed(org.jgrapht.event.VertexTraversalEvent)
	 */
	@Override
	public void vertexTraversed(VertexTraversalEvent<User> arg0) {
		
		super.vertexTraversed(arg0);
		
		User vertex = arg0.getVertex();
		
		if (!hashtable.containsKey(vertex.getLogin()))
		{
			hashtable.put(vertex.getLogin(), new Hashtable<Integer, Set<User>>());
		}
	}
	
	public Set<User> getNeighboursAtLevel(String vertex, int level)
	{
		if (containsNeighboursAtLevel(vertex, level))
			return hashtable.get(vertex).get(level);
		
		return null;
	}
	
	public boolean containsNeighboursAtLevel(String vertex, int level)
	{
		return (hashtable.containsKey(vertex) && hashtable.get(vertex).containsKey(level)); 
	}
	
	public Set<String> getAllVertex()
	{
		return hashtable.keySet();
	}
	
	public void cacheFriendshipLevels()
	{
		DepthFirstIterator<User, CustomEdge> iterator = new DepthFirstIterator<User, CustomEdge>(graph);
		
		iterator.setCrossComponentTraversal(true);
		iterator.addTraversalListener(this);
		
		while (iterator.hasNext())
			iterator.next();
	}
}
